using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;

using HadronData;

using Hadron.Entities;
using Hadron.SpecialEffects;

namespace Hadron
{
    class Environment
    {
        private GameContext m_gameContext;
        private Vector2 m_vecCameraTarget;
        private float m_fCameraZoom;
        private PlayerShip m_playerShip;
        private Level m_level;
        private List<Projectile> m_listPlayerProjectiles;
        private List<Projectile> m_listEnemyProjectiles;
        private List<SpecialEffect> m_listSpecialEffects;
        private List<Entity> m_listEntities;
        private Vector2 m_vecGravity;
        private bool m_bObjectivesSatisfied;
        private bool m_bLevelComplete;
        private bool m_bCountdownActive;
        private float m_fCountdown;
        private float m_fWhiteOut;
        private SoundEffectInstance m_soundEffectInstanceCountdown;

        private void CheckObjectives(GameTime p_gameTime)
        {
            if (m_bCountdownActive)
            {
                float fDeltaTime = (float)p_gameTime.ElapsedGameTime.TotalSeconds;
                m_fCountdown -= fDeltaTime;
                if (m_fCountdown <= 0.0f)
                {
                    m_playerShip.Integrity = 0.0f;
                    m_fCountdown += (float) m_level.Objectives.Countdown;
                }

                if (m_fCountdown >= 0.0f && m_fCountdown <= 1.0f)
                    m_fWhiteOut = 1.0f - m_fCountdown;
                else
                    m_fWhiteOut -= m_fWhiteOut * fDeltaTime * 2.0f;
            }

            if (m_bObjectivesSatisfied)
                return;

            LevelObjectives levelObjectives = m_level.Objectives; ;

            if (m_playerShip.ReactorsDestroyed >= levelObjectives.MinimumReactorsDestroyed
                && m_level.Objectives.Countdown > 0 && !m_bCountdownActive)
            {
                // trigger countdown if min reactors destroyed and if applicable
                m_fCountdown = (float) m_level.Objectives.Countdown;
                m_bCountdownActive = true;
                m_soundEffectInstanceCountdown
                    = AssetManager.Sounds.Countdown.Play(1.0f, 0.0f, 0.0f, true);
            }

            // crate quota
            if (m_playerShip.CratesDelivered < levelObjectives.MinimumCratesDelivered)
                return;

            // scientist quota
            if (m_playerShip.ScientistsRescued < levelObjectives.MinimumScientistsRescued)
                return;

            // reactor quota
            if (m_playerShip.ReactorsDestroyed < levelObjectives.MinimumReactorsDestroyed)
                return;

            // check if all enemies destroyed if applicable
            if (levelObjectives.DestroyAllEnemies)
                foreach (Entity entity in m_listEntities)
                    if (entity.Hostile && entity.Destructuble)
                        return;

            m_bObjectivesSatisfied = true;

            // find and open exit portals
            foreach (Entity entity in m_listEntities)
            {
                if (entity.Type != EntityType.ExitPortal)
                    continue;

                Portal portal = (Portal)entity;
                portal.Open = true;
            }
        }

        private void UpdateCamera(GameTime p_gameTime)
        {
            float fDeltaTime = (float)p_gameTime.ElapsedGameTime.TotalSeconds;
            Vector2 vecOffset = m_playerShip.Position - m_vecCameraTarget;
            m_vecCameraTarget += vecOffset * 2.0f * fDeltaTime;

            // zoom
            m_fCameraZoom = 1.5f - Math.Min(0.5f, vecOffset.Length() * 0.01f);

            // level limits
            m_vecCameraTarget.X = Math.Max(400.0f, m_vecCameraTarget.X);
            m_vecCameraTarget.Y = Math.Max(300.0f, m_vecCameraTarget.Y);
            m_vecCameraTarget.X = Math.Min(m_level.Dimensions.X - 400.0f, m_vecCameraTarget.X);
            m_vecCameraTarget.Y = Math.Min(m_level.Dimensions.Y - 300.0f, m_vecCameraTarget.Y);
        }

        private void UpdatePlayerShip(GameTime p_gameTime)
        {
            // player ship movement
            m_playerShip.Update(p_gameTime);

            // generic list of contact points
            List<ContactPoint> listContactPoints = new List<ContactPoint>();

            // activate landing gear if landing sensor is detecting ground and ship is
            // almost aligned
            listContactPoints.Clear();
            m_level.ComputeContactPoints(m_playerShip.LandingSensor, listContactPoints);
            if (listContactPoints.Count > 0)
            {
                ContactPoint contactPoint = listContactPoints[0];
                m_playerShip.LandingGear = Vector2.Dot(contactPoint.Normal, m_playerShip.Direction) > 0.95f;

                if (m_playerShip.LandingGear && !m_playerShip.ForwardThruster)
                {
                    float fDeltaTime = (float) p_gameTime.ElapsedGameTime.TotalSeconds;

                    float fEdgeNormalAngle = (float)Math.Atan2(contactPoint.Normal.Y, contactPoint.Normal.X);
                    float fDeltaAngle = fEdgeNormalAngle - m_playerShip.Orientation;
                    if (fDeltaAngle > MathHelper.Pi)
                        fDeltaAngle -= MathHelper.TwoPi;
                    if (fDeltaAngle < -MathHelper.Pi)
                        fDeltaAngle += MathHelper.TwoPi;
                    m_playerShip.Orientation += fDeltaAngle * fDeltaTime * 2.0f;
                }
            }
            else
                m_playerShip.LandingGear = false;

            // ship collision with level

            // quick bounding test
            if (!m_level.Intersects(m_playerShip.BoundingCircle))
                return;

            listContactPoints.Clear();
            if (m_playerShip.Shield)
                m_level.ComputeContactPoints(m_playerShip.ShieldGeometry, listContactPoints);
            else
            {
                m_level.ComputeContactPoints(m_playerShip.HullGeometry, listContactPoints);
                if (listContactPoints.Count > 0)
                {
                    float fNormalSpeed = Vector2.Dot(m_playerShip.Velocity, listContactPoints[0].Normal);
                    if (fNormalSpeed < 0.0f)
                        m_playerShip.Integrity += (int)(fNormalSpeed * 0.1f);

                    // sparks
                    ProjectileBlast projectileBlast
                        = new ProjectileBlast(this, listContactPoints[0].Position, Color.OrangeRed);
                    projectileBlast.ParticleSystem.EmissionVelocity = listContactPoints[0].Normal * 16.0f;
                    projectileBlast.ParticleSystem.EmissionVelocityVariance = 16.0f;
                    m_listSpecialEffects.Add(projectileBlast);
                }
            }

            if (m_playerShip.LandingGear)
                m_level.ComputeContactPoints(m_playerShip.LandingGearGeometry, listContactPoints);

            foreach (ContactPoint contactPoint in listContactPoints)
            {
                float fSpeedNormal = Vector2.Dot(m_playerShip.Velocity, contactPoint.Normal);
                if (fSpeedNormal < 0.0f)
                {
                    Vector2 vecVelocityNormal = contactPoint.Normal * fSpeedNormal;
                    Vector2 vecVelocityTangent = m_playerShip.Velocity - vecVelocityNormal;
                    m_playerShip.Velocity = (vecVelocityTangent - vecVelocityNormal) * 0.5f;
                    m_playerShip.Position += contactPoint.Normal * 0.25f;
                }
            }
        }

        private void UpdatePlayerProjectiles(GameTime p_gameTime)
        {
            // generic list of contact points
            List<ContactPoint> listContactPoints = new List<ContactPoint>();

            // player projectile movement
            for (int nIndex = m_listPlayerProjectiles.Count - 1; nIndex >= 0; nIndex--)
            {
                Projectile projectile = m_listPlayerProjectiles[nIndex];
                projectile.Update(p_gameTime);

                // collision with level

                // quick bounding circle test
                if (m_level.Intersects(projectile.BoundingCircle))
                {
                    // detailed collision test
                    listContactPoints.Clear();
                    m_level.ComputeContactPoints(projectile.Geometry, listContactPoints);
                    if (listContactPoints.Count > 0)
                    {
                        projectile.Lifetime = 0.0f;

                        // spawn blast
                        ProjectileBlast projectileBlast = new ProjectileBlast(this, listContactPoints[0].Position, Color.Cyan);
                        projectileBlast.ParticleSystem.EmissionVelocity
                            = ReflectVector(listContactPoints[0].Normal, projectile.Velocity) * 0.1f;
                        projectileBlast.ParticleSystem.EmissionVelocityVariance
                            = projectileBlast.ParticleSystem.EmissionVelocity.Length() * 0.5f;
                        m_listSpecialEffects.Add(projectileBlast);
                    }
                }

                if (projectile.Lifetime <= 0.0f)
                    m_listPlayerProjectiles.RemoveAt(nIndex);
            }

        }

        private void UpdateEnemyProjectiles(GameTime p_gameTime)
        {
            // generic list of contact points
            List<ContactPoint> listContactPoints = new List<ContactPoint>();

            // enemy projectile movement
            for (int nIndex = m_listEnemyProjectiles.Count - 1; nIndex >= 0; nIndex--)
            {
                Projectile projectile = m_listEnemyProjectiles[nIndex];

                projectile.Update(p_gameTime);

                // collision with level

                // quick bounding circle test
                if (m_level.Intersects(projectile.BoundingCircle))
                {
                    // detailed collision test
                    listContactPoints.Clear();
                    m_level.ComputeContactPoints(projectile.Geometry, listContactPoints);
                    if (listContactPoints.Count > 0)
                    {
                        projectile.Lifetime = 0.0f;
                    }
                }

                // collision with player ship
                if (projectile.BoundingCircle.Intersects(m_playerShip.BoundingCircle))
                {
                    if (m_playerShip.Shield)
                    {
                        // shield
                        if (projectile.Geometry.Intersects(m_playerShip.ShieldGeometry))
                            projectile.Lifetime = 0.0f;
                    }
                    else
                    {
                        // hull contact
                        if (m_playerShip.HullGeometry.Intersects(projectile.Geometry))
                        {
                            projectile.Lifetime = 0.0f;
                            m_playerShip.Integrity -= 10.0f;
                        }
                    }
                }

                if (projectile.Lifetime <= 0.0f)
                    m_listEnemyProjectiles.RemoveAt(nIndex);
            }
        }

        private void UpdateSpecialEffects(GameTime p_gameTime)
        {
            // special effects
            for (int nIndex = 0; nIndex < m_listSpecialEffects.Count; )
            {
                SpecialEffect specialEffect = m_listSpecialEffects[nIndex];
                specialEffect.Update(p_gameTime);

                if (specialEffect.Lifetime > 0.0f)
                {
                    specialEffect.Update(p_gameTime);
                    nIndex++;
                }
                else
                    m_listSpecialEffects.RemoveAt(nIndex);
            }
        }

        private void UpdateEntities(GameTime p_gameTime)
        {
            for (int nIndex = 0; nIndex < m_listEntities.Count; )
            {
                Entity entity = m_listEntities[nIndex];

                // eliminate deactivated entities
                if (entity.Status == EntityStatus.Deactivated)
                {
                    m_listEntities.RemoveAt(nIndex);
                    continue;
                }

                // eliminate destroyed entities and spawn explosions
                if (entity.Status == EntityStatus.Destroyed)
                {
                    // score
                    m_gameContext.Score += entity.Points;

                    // event
                    entity.OnDestroy();

                    // spawn explosion
                    Explosion explosion = new Explosion(this, entity.Position, 1.0f, 2.0f);
                    m_listSpecialEffects.Add(explosion);

                    // remove entity
                    m_listEntities.RemoveAt(nIndex);
                    continue;
                }

                // compute entity activity status
                entity.Status = (entity.Position - m_playerShip.Position).Length() < 1024.0f
                    ? EntityStatus.Active : EntityStatus.Waiting;

                // skip to next entity if current is inactive
                if (entity.Status == EntityStatus.Waiting)
                {
                    ++nIndex;
                    continue;
                }

                // update entity
                entity.Update(p_gameTime);

                // if entity hostile check collisions with projectiles
                if (entity.Hostile)
                {
                    foreach (PlayerProjectile playerProjectile in m_listPlayerProjectiles)
                    {
                        // quick bounding circle test
                        if (!playerProjectile.BoundingCircle.Intersects(entity.BoundingCircle))
                            continue;

                        // detailed collision test
                        if (!playerProjectile.Geometry.Intersects(entity.Geometry))
                            continue;

                        if (entity.Destructuble)
                            entity.ApplyDamage(1);
                        playerProjectile.Lifetime = 0.0f;

                        // spawn blast
                        ProjectileBlast playerProjectileBlast = new ProjectileBlast(this, playerProjectile.Position, Color.Orange);
                        playerProjectileBlast.ParticleSystem.EmissionVelocity = -playerProjectile.Velocity * 0.1f;
                        m_listSpecialEffects.Add(playerProjectileBlast);

                        // hit event
                        entity.OnProjectileHit();
                    }
                }

                // test for collisions against player (shield effective only for projectiles)
                if (m_playerShip.Status == PlayerShipStatus.Active
                    && m_playerShip.BoundingCircle.Intersects(entity.BoundingCircle))
                {
                    if (m_playerShip.HullGeometry.Intersects(entity.Geometry))
                    {
                        if (entity.Hostile)
                        {
                            m_playerShip.Integrity -= entity.Health;
                            if (entity.Destructuble)
                                entity.ApplyDamage(1);
                        }

                        // hit event
                        entity.OnPlayerHit();
                    }
                }

                ++nIndex;
            }
        }

        public Environment(GameContext p_gameContext)
        {
            m_gameContext = p_gameContext;
            m_vecCameraTarget = Vector2.Zero;
            m_fCameraZoom = 1.0f;
            m_playerShip = null;
            m_level = null;
            m_listPlayerProjectiles = new List<Projectile>();
            m_listEnemyProjectiles = new List<Projectile>();
            m_listSpecialEffects = new List<SpecialEffect>();
            m_listEntities = new List<Entity>();
            m_bObjectivesSatisfied = false;
            m_bCountdownActive = false;
            m_fCountdown = 0.0f;
            m_fWhiteOut = 0.0f;
        }

        public Vector2 ReflectVector(Vector2 p_vecNormal, Vector2 p_vecIncoming)
        {
            Vector2 vecTangent = new Vector2(-p_vecNormal.Y, p_vecNormal.X);
            return vecTangent * Vector2.Dot(vecTangent, p_vecIncoming)
                - p_vecNormal * Vector2.Dot(p_vecNormal, p_vecIncoming);
        }

        public float ComputeSoundVolume(Vector2 p_vecSoundSource)
        {
            return Math.Max(0.0f, 1.0f - (p_vecSoundSource - m_vecCameraTarget).Length() * 0.002f); ;
        }

        public Portal GetNearestEntryPortal(PlayerShip p_playerShip)
        {
            float fClosestDistance = float.MaxValue;
            Portal portal = null;
            foreach (Entity entity in m_listEntities)
            {
                if (entity.Type != EntityType.EntryPortal)
                    continue;

                float fDistance = (m_playerShip.Position - entity.Position).Length();
                if (fClosestDistance > fDistance)
                {
                    portal = (Portal) entity;
                    fClosestDistance = fDistance;
                }
            }

            return portal;
        }

        public void InitialiseEntities()
        {
            m_listEntities.Clear();
            m_vecGravity = Vector2.Zero;
            foreach (EntityMarker entityMarker in m_level.EntityMarkers)
            {
                Entity entity = Entity.Create(entityMarker, this);
                m_listEntities.Add(entity);
            }
        }

        public void TerminateEntities()
        {
            foreach (Entity entity in m_listEntities)
                if (entity.Status != EntityStatus.Deactivated)
                    entity.OnDeactivate();

            m_playerShip.Terminate();

            if (m_soundEffectInstanceCountdown != null)
            {
                m_soundEffectInstanceCountdown.Stop();
                m_soundEffectInstanceCountdown = null;
            }
        }

        public void Update(GameTime p_gameTime)
        {
            // update camera
            UpdateCamera(p_gameTime);

            // update player movement
            UpdatePlayerShip(p_gameTime);

            // update environment
            m_level.Update(p_gameTime);

            // update player projectiles
            UpdatePlayerProjectiles(p_gameTime);

            // update enemy projectiles
            UpdateEnemyProjectiles(p_gameTime);

            // update special effects
            UpdateSpecialEffects(p_gameTime);

            // update entities
            UpdateEntities(p_gameTime);

            // check objectives
            CheckObjectives(p_gameTime);
        }

        public void Draw(SpriteBatch p_spriteBatch)
        {
            GraphicsDevice graphicsDevice = p_spriteBatch.GraphicsDevice;

            // camera
            Matrix matrix = Matrix.Identity;
            matrix.M41 = -m_vecCameraTarget.X * m_fCameraZoom + graphicsDevice.Viewport.Width * 0.5f;
            matrix.M42 = -m_vecCameraTarget.Y * m_fCameraZoom + graphicsDevice.Viewport.Height * 0.5f;
            matrix.M11 = matrix.M22 = m_fCameraZoom;

            // camera shake
            if (m_bCountdownActive)
            {
                Random random = new Random();
                matrix.M41 += (float)random.NextDouble() * 4.0f - 2.0f;
                matrix.M42 += (float)random.NextDouble() * 4.0f - 2.0f;
            }

            // handle whiteout / background
            graphicsDevice.Clear(new Color(m_fWhiteOut, m_fWhiteOut, m_fWhiteOut));

            p_spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Texture, SaveStateMode.None, matrix);

            m_level.Draw(p_spriteBatch);

            m_playerShip.Draw(p_spriteBatch);

            foreach (Projectile projectile in m_listPlayerProjectiles)
                projectile.Draw(p_spriteBatch);

            foreach (Projectile projectile in m_listEnemyProjectiles)
                projectile.Draw(p_spriteBatch);

            foreach (Entity entity in m_listEntities)
                if (entity.Status == EntityStatus.Active)
                    entity.Draw(p_spriteBatch);

            foreach (SpecialEffect specialEffect in m_listSpecialEffects)
                specialEffect.Draw(p_spriteBatch);

            p_spriteBatch.End();
        }

        public GameContext Context
        {
            get { return m_gameContext; }
        }

        public Vector2 CameraTarget
        {
            get { return m_vecCameraTarget; }
            set { m_vecCameraTarget = value; }
        }

        public PlayerShip PlayerShip
        {
            get { return m_playerShip; }
            set { m_playerShip = value; }
        }

        public Level CurrentLevel
        {
            get { return m_level; }
            set { m_level = value; }
        }

        public List<Projectile> PlayerProjectiles
        {
            get { return m_listPlayerProjectiles; }
        }

        public List<Projectile> EnemyProjectiles
        {
            get { return m_listEnemyProjectiles; }
        }

        public List<Entity> Entities
        {
            get { return m_listEntities; }
            set { m_listEntities = value; }
        }

        public List<SpecialEffect> SpecialEffects
        {
            get { return m_listSpecialEffects; }
        }

        public Vector2 Gravity
        {
            get { return m_vecGravity; }
            set { m_vecGravity = value; }
        }

        public bool CountdownActive
        {
            get { return m_bCountdownActive; }
        }

        public float Countdown
        {
            get { return m_fCountdown; }
        }

        public bool LevelComplete
        {
            get { return m_bLevelComplete; }
            set { m_bLevelComplete = value; }
        }
    }
}
